package com.example.tongyao.system.scheduling.config;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.example.tongyao.system.scheduling.entity.SysTimedTask;
import com.example.tongyao.system.scheduling.entity.SysTimedTaskLog;
import com.example.tongyao.system.scheduling.service.ISysTimedTaskLogService;
import com.example.tongyao.system.scheduling.service.ISysTimedTaskService;
import com.example.tongyao.utils.DateUtils;
import com.example.tongyao.utils.SpringUtil;
import com.example.tongyao.utils.tongyao.TyUtils;
import lombok.extern.log4j.Log4j2;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;

import javax.annotation.PreDestroy;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledFuture;

@Log4j2
@Configuration
@EnableScheduling
public class TimedTaskConfig implements SchedulingConfigurer {

    @Autowired
    private ISysTimedTaskService iSysTimedTaskService;
    @Autowired
    private ISysTimedTaskLogService iSysTimedTaskLogService;




    private volatile ScheduledTaskRegistrar taskRegistrar;
    private final ConcurrentHashMap<Integer, ScheduledFuture<?>> scheduledFutures = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<Integer, SysTimedTask> tasks = new ConcurrentHashMap<>();


    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {

        taskRegistrar.setScheduler(Executors.newScheduledThreadPool(5));
        this.taskRegistrar = taskRegistrar;
        refreshAll();
    }

    public void refreshAll() {
        Map map = new HashMap();
        map.put("state","1");
        List<SysTimedTask> jobs = iSysTimedTaskService.listByMap(map);

        Set<Integer> ids = scheduledFutures.keySet();
        for (Integer id : ids) {
            if (!isExist(id, jobs)) {
                scheduledFutures.get(id).cancel(false);
                scheduledFutures.remove(id);
            }
        }

        for (SysTimedTask job: jobs) {
            if(StringUtils.isBlank(job.getCron())) {
                continue;
            }
            Integer jobId = job.getJobId();
            if(scheduledFutures.containsKey(jobId) && tasks.get(jobId).equals(job)) {
                continue;
            }
            if (scheduledFutures.containsKey(jobId)) {
                scheduledFutures.get(jobId).cancel(false);
                scheduledFutures.remove(jobId);
                tasks.remove(jobId);
            }
            ScheduledFuture<?> schedule = taskRegistrar.getScheduler().schedule(getRunnable(job), getTrigger(job));
            scheduledFutures.put(jobId,schedule);
            tasks.put(jobId,job);
        }
    }


    public void execTask(SysTimedTask job) throws ClassNotFoundException {
        String params = job.getParams();
        String[] paramArray = null;
        Class<?>[] paramTypeArray = new Class[0];
        if (StringUtils.isNotBlank(params)) {
            paramArray = params.split("\\s*,|，\\s*");
            int length = paramArray.length;
            paramTypeArray = new Class[length];
            for (int i = 0; i< length; i++) {
                paramTypeArray[i]=String.class;
            }
        }
        Class<?> clazz = Class.forName(job.getClassName());
        String className = lowerFirstCapse(clazz.getSimpleName());
        Object bean = SpringUtil.getBean(className);
        Method method = ReflectionUtils.findMethod(bean.getClass(), job.getMethod(),paramTypeArray);
        Assert.notNull(method,bean.getClass()+"#"+job.getMethod()+" not exists");
        ReflectionUtils.invokeMethod(method, bean, paramArray);
    }

    private Runnable getRunnable(SysTimedTask job) {

        return new Runnable() {
            @Override
            public void run() {

                //日志记录
                SysTimedTaskLog jobLog = new SysTimedTaskLog();
                jobLog.setExecType(1);
                jobLog.setExecStatus(1);
                jobLog.setCreated(DateUtils.getDateTime());

                BeanUtils.copyProperties(job,jobLog);


                try {
                    execTask(job);
                } catch (Exception e) {
                    log.error(e.getMessage(), e);


                    jobLog.setExecStatus(0);
                    jobLog.setExecMsg(pringStackTraceToString(e));
                } finally {
                    iSysTimedTaskLogService.save(jobLog);
                }
            }
        };
    }

    private Trigger getTrigger(SysTimedTask job) {
        return new Trigger() {
            @Override
            public Date nextExecutionTime(TriggerContext triggerContext) {
                CronTrigger trigger = new CronTrigger(job.getCron());
                Date nextExec = trigger.nextExecutionTime(triggerContext);
                return nextExec;
            }
        };
    }

    /**
     * 转换首字母小写
     *
     * @param str
     * @return
     */
    private static String lowerFirstCapse(String str) {
        char[] chars = str.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

    private boolean isExist(Integer id, List<SysTimedTask> jobs) {
        for(SysTimedTask job: jobs) {
            if(id.equals(job.getJobId())) {
                return true;
            }
        }
        return false;
    }

    private static String pringStackTraceToString(Throwable t) {
        StringWriter sw = new StringWriter();
        t.printStackTrace( new PrintWriter(sw, true));
        return sw.getBuffer().toString();
    }

    @PreDestroy
    public void destory() {
        taskRegistrar.destroy();
    }
}
